import platform
import subprocess
import os
import shutil
import hashlib
import threading
import logging
import time
import multiprocessing
import atexit
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
from functools import lru_cache

from flask import Flask, render_template, redirect, url_for, request, send_from_directory, flash, jsonify, abort, make_response
from werkzeug.utils import secure_filename
from cachetools import TTLCache
from datetime import datetime, timedelta

app = Flask(__name__, static_folder='app/static', template_folder='app/templates')
# 加载配置文件
app.config.from_pyfile("config.py")

# 设置 SECRET_KEY 用于 flash 消息
app.secret_key = app.config.get('SECRET_KEY', 'development_secret_key')

# 获取相册路径
ALBUMS_FOLDER = None

# 允许的图片文件扩展名
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}


# 配置日志
def setup_logging():
    """设置日志配置"""
    # 创建自定义格式器
    formatter = logging.Formatter(
        '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
    )

    # 创建文件处理器
    file_handler = logging.FileHandler('logs/album_viewer.log', encoding='utf-8')
    file_handler.setLevel(logging.INFO)
    file_handler.setFormatter(formatter)

    # 创建控制台处理器
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.WARNING)  # 控制台只显示警告和错误
    console_handler.setFormatter(formatter)

    # 创建错误文件处理器
    error_handler = logging.FileHandler('logs/album_viewer_errors.log', encoding='utf-8')
    error_handler.setLevel(logging.ERROR)
    error_handler.setFormatter(formatter)

    # 配置根日志器
    root_logger = logging.getLogger()
    root_logger.setLevel(logging.INFO)
    root_logger.addHandler(file_handler)
    root_logger.addHandler(console_handler)
    root_logger.addHandler(error_handler)

    return logging.getLogger(__name__)


logger = setup_logging()


# 添加Flask应用日志记录
class RequestLogger:
    """请求日志记录中间件"""

    def __init__(self, app):
        self.app = app
        self.init_app(app)

    def init_app(self, app):
        app.before_request(self.log_request)
        app.after_request(self.log_response)
        app.errorhandler(404)(self.handle_404)
        app.errorhandler(500)(self.handle_500)

    def log_request(self):
        user_agent = request.headers.get('User-Agent', 'Unknown')
        logger.info("Request: {} {} - IP: {} - User-Agent: {}".format(
            request.method, request.path, request.remote_addr, user_agent))

    def log_response(self, response):
        logger.info("Response: {} {} - Status: {}".format(
            request.method, request.path, response.status_code))
        return response

    def handle_404(self, error):
        logger.warning("404 Error: {} {} - IP: {}".format(
            request.method, request.path, request.remote_addr))
        flash('页面未找到', 'error')
        return redirect(url_for('index')), 404

    def handle_500(self, error):
        logger.error("500 Error: {} {} - IP: {} - Error: {}".format(
            request.method, request.path, request.remote_addr, str(error)))
        flash('服务器内部错误', 'error')
        return redirect(url_for('index')), 500


class CacheMiddleware:
    """HTTP缓存中间件（已移除压缩功能）"""

    def __init__(self, app):
        self.app = app
        self.init_app(app)

    def init_app(self, app):
        app.after_request(self.add_cache_headers)
        # 暂时禁用压缩功能，避免与Flask响应处理冲突
        # app.after_request(self.compress_response)

    def add_cache_headers(self, response):
        """添加适当的缓存头"""
        # 静态文件缓存策略
        if request.endpoint == 'custom_static':
            # 图片文件缓存1年
            response.headers['Cache-Control'] = 'public, max-age=31536000, immutable'
            response.headers['Expires'] = (datetime.now() + timedelta(days=365)).strftime('%a, %d %b %Y %H:%M:%S GMT')

            # 不再生成ETag，避免访问response.data导致的错误
            # ETag需要读取响应数据，但在direct passthrough模式下会导致错误

        # API响应缓存策略
        elif request.endpoint in ['get_album_counts', 'api_count_images']:
            response.headers['Cache-Control'] = 'public, max-age=300'  # 5分钟缓存

        # HTML页面缓存策略
        elif request.endpoint in ['index', 'view_album']:
            response.headers['Cache-Control'] = 'public, max-age=60'  # 1分钟缓存

        return response


# 初始化中间件
request_logger = RequestLogger(app)
cache_middleware = CacheMiddleware(app)


# 创建优化的线程池

def create_optimized_thread_pool():
    """创建优化的线程池"""
    # 根据CPU核心数动态调整线程数
    cpu_count = multiprocessing.cpu_count()
    max_workers = min(32, (cpu_count or 1) + 4)  # 限制最大线程数为32

    logger.info("创建线程池，工作线程数: {}".format(max_workers))
    return ThreadPoolExecutor(max_workers=max_workers, thread_name_prefix="AlbumWorker")


executor = create_optimized_thread_pool()


# 添加应用关闭时的清理

def cleanup_resources():
    """清理资源"""
    logger.info("正在关闭线程池...")
    executor.shutdown(wait=True)
    logger.info("资源清理完成")


atexit.register(cleanup_resources)

# 相册图片计数缓存（改用TTL缓存）
# 使用TTL缓存，最多缓存500个相册，过期时间1小时
album_counts_cache = TTLCache(maxsize=500, ttl=3600)

# 相册列表缓存
albums_list_cache = {'data': None, 'timestamp': 0, 'ttl': 300}  # 5分钟过期


def is_image_file(filename):
    """
    判断是否是图片文件
    
    Args:
        filename: 文件名称
        
    Returns:
        bool: 是否为图片文件
    """
    if not filename or '.' not in filename:
        return False

    extension = filename.rsplit('.', 1)[1].lower()
    return extension in ALLOWED_EXTENSIONS


def count_images_in_album(album_path):
    """
    计算相册中的图片数量（安全版本，不使用shell命令）
    
    Args:
        album_path: 相册路径
        
    Returns:
        int: 图片数量
    """
    image_count = 0

    try:
        # 验证路径安全性
        if not album_path.exists() or not album_path.is_dir():
            logger.warning("相册路径无效: {}".format(album_path))
            return 0

        # 使用os.scandir提高性能，避免shell命令注入
        with os.scandir(album_path) as entries:
            for entry in entries:
                if entry.is_file() and is_image_file(entry.name):
                    image_count += 1

    except PermissionError:
        logger.warning("无权限访问目录: {}".format(album_path))
        return 0
    except Exception as e:
        logger.error("计算图片数量时出错: {} - {}".format(album_path, str(e)))
        return 0

    # 更新缓存
    album_counts_cache[album_path.name] = image_count
    logger.info("相册 {} 包含 {} 张图片".format(album_path.name, image_count))
    return image_count


def get_albums():
    """
    获取所有相册文件夹（使用缓存优化）
    
    Returns:
        list: 相册信息列表，每个元素包含名称和封面图
    """
    current_time = time.time()

    # 检查缓存是否有效
    if (albums_list_cache['data'] is not None and
            current_time - albums_list_cache['timestamp'] < albums_list_cache['ttl']):
        logger.debug("使用缓存的相册列表")
        return albums_list_cache['data']

    logger.info("重新构建相册列表")
    albums = []

    try:
        albums_dir = Path(ALBUMS_FOLDER)

        if not albums_dir.exists():
            logger.warning("相册根目录不存在: {}".format(ALBUMS_FOLDER))
            return []

        # 使用os.scandir提高性能
        with os.scandir(albums_dir) as entries:
            for entry in entries:
                if entry.is_dir():
                    album_name = entry.name

                    # 跳过隐藏目录和系统目录
                    if album_name.startswith('.') or album_name.startswith('__'):
                        continue

                    try:

                        # 获取相册封面（第一张图片）
                        cover_image = get_album_cover(entry.path)

                        # 如果没有图片，跳过这个相册
                        if cover_image is None:
                            continue

                        # 从缓存中获取图片数量
                        image_count = album_counts_cache.get(album_name, 0)

                        albums.append({
                            'name': album_name,
                            'cover': cover_image,
                            'image_count': image_count
                        })

                        # 启动异步任务更新图片计数（如果没有缓存）
                        if album_name not in album_counts_cache:
                            executor.submit(count_images_in_album, Path(entry.path))

                    except ValueError as e:
                        logger.warning("跳过无效相册: {} - {}".format(album_name, str(e)))
                        continue

    except PermissionError:
        logger.error("无权限访问相册根目录: {}".format(ALBUMS_FOLDER))
        return []
    except Exception as e:
        logger.error("获取相册列表时出错: {}".format(str(e)))
        return []

    # 按相册名称排序
    albums.sort(key=lambda x: x['name'])

    # 更新缓存
    albums_list_cache['data'] = albums
    albums_list_cache['timestamp'] = current_time

    logger.info("找到 {} 个相册".format(len(albums)))
    return albums


def get_album_cover(album_path):
    """
    获取相册封面图片
    
    Args:
        album_path: 相册路径
        
    Returns:
        str: 封面图片文件名，如果没有图片则返回None
    """
    try:
        with os.scandir(album_path) as entries:
            for entry in entries:
                if entry.is_file() and is_image_file(entry.name):
                    # 简单检查文件名是否安全（不包含路径遍历字符）
                    if '..' not in entry.name and '/' not in entry.name and '\\' not in entry.name:
                        return entry.name
    except (PermissionError, OSError):
        logger.warning("无法访问相册目录: {}".format(album_path))

    return None


def get_album_images(album_name):
    """
    获取指定相册中的所有图片（安全版本）
    
    Args:
        album_name: 相册名称
        
    Returns:
        list: 图片信息列表
    """
    try:

        images = []
        album_dir = Path(ALBUMS_FOLDER) / album_name

        # 验证路径安全性
        try:
            album_dir.resolve().relative_to(Path(ALBUMS_FOLDER).resolve())
        except ValueError:
            logger.warning("试图访问非法路径: {}".format(album_dir))
            return []

        if not album_dir.exists() or not album_dir.is_dir():
            return []

        # 使用os.scandir提高性能并确保安全性
        try:
            with os.scandir(album_dir) as entries:
                for entry in entries:
                    if entry.is_file() and is_image_file(entry.name):
                        # 简单检查文件名是否安全（不包含路径遍历字符）
                        if '..' not in entry.name and '/' not in entry.name and '\\' not in entry.name:
                            images.append({
                                'filename': entry.name,
                                'url': "{}/{}".format(album_name, entry.name)
                            })
        except PermissionError:
            logger.warning("无权限访问相册目录: {}".format(album_dir))
            return []

        # 按文件名排序
        images.sort(key=lambda x: x['filename'])

        logger.info("相册 {} 包含 {} 张图片".format(album_name, len(images)))
        return images

    except ValueError as e:
        logger.warning("无效的相册名称: {} - {}".format(album_name, str(e)))
        return []
    except Exception as e:
        logger.error("获取相册图片时出错: {} - {}".format(album_name, str(e)))
        return []


def get_albums_path():
    """
    获取相册路径
    
    @returns {string} 根据操作系统返回相应的相册路径
    """
    sysstr = platform.system()
    if sysstr == "Windows":
        return app.config['ALBUMS_FOLDER_WIN']
    else:
        return app.config['ALBUMS_FOLDER_MAC']


def pre_startup_check():
    """
    启动前检查
    
    @returns {void}
    """
    global ALBUMS_FOLDER
    ALBUMS_FOLDER = get_albums_path()
    # 确保相册文件夹存在
    os.makedirs(ALBUMS_FOLDER, exist_ok=True)


@app.route('/')
def index():
    """
    显示相册列表（使用缓存）
    
    Returns:
        Response: 渲染后的模板响应
    """
    try:
        albums = get_albums()
        return render_template('album_list.html', albums=albums, album_dir=ALBUMS_FOLDER)
    except Exception as e:
        logger.error("首页加载失败: {}".format(str(e)))
        flash('加载相册列表时出错', 'error')
        return render_template('album_list.html', albums=[], album_dir=ALBUMS_FOLDER)


@app.route('/album/<album_name>')
def view_album(album_name):
    """
    以幻灯片形式显示相册中的图片
    
    Args:
        album_name: 相册名称
        
    Returns:
        Response: 渲染后的模板响应或重定向
    """
    try:

        images = get_album_images(album_name)
        if not images:
            flash('相册 "{}" 不存在或为空'.format(album_name), 'error')
            return redirect(url_for('index'))

        # 计算相册名称的MD5值
        album_md5 = hashlib.md5(album_name.encode('utf-8')).hexdigest()

        logger.info("用户访问相册: {}".format(album_name))
        return render_template('slideshow.html', album_name=album_name, images=images, album_md5=album_md5)

    except ValueError as e:
        logger.warning("无效的相册名称: {} - {}".format(album_name, str(e)))
        flash('无效的相册名称: {}'.format(str(e)), 'error')
        return redirect(url_for('index'))
    except Exception as e:
        logger.error("访问相册时出错: {} - {}".format(album_name, str(e)))
        flash('访问相册时出错', 'error')
        return redirect(url_for('index'))


@app.route('/delete_album/<album_name>', methods=['POST'])
def delete_album(album_name):
    """
    删除相册及其本地文件夹
    
    Args:
        album_name: 相册名称
        
    Returns:
        Response: 重定向响应
    """
    try:
        album_path = Path(ALBUMS_FOLDER) / album_name

        # 验证路径安全性，确保在指定目录内
        try:
            album_path.resolve().relative_to(Path(ALBUMS_FOLDER).resolve())
        except ValueError:
            logger.warning("试图访问非法路径: {}".format(album_path))
            flash('无效的相册路径', 'error')
            return redirect(url_for('index'))

        if album_path.exists() and album_path.is_dir():
            try:
                # 使用shutil.rmtree删除整个文件夹及其内容
                shutil.rmtree(album_path)

                # 清理相关缓存
                if album_name in album_counts_cache:
                    del album_counts_cache[album_name]

                # 清理相册列表缓存
                albums_list_cache['data'] = None

                logger.info("已删除相册: {}".format(album_name))
                flash('相册 "{}" 已成功删除'.format(album_name), 'success')

            except PermissionError:
                logger.error("删除相册时权限不足: {}".format(album_name))
                flash('权限不足，无法删除相册', 'error')
            except Exception as e:
                logger.error("删除相册时出错: {} - {}".format(album_name, str(e)))
                flash('删除相册时出错: {}'.format(str(e)), 'error')
        else:
            logger.warning("试图删除不存在的相册: {}".format(album_name))
            flash('相册 "{}" 不存在'.format(album_name), 'error')

    except ValueError as e:
        logger.warning("无效的相册名称: {} - {}".format(album_name, str(e)))
        flash('无效的相册名称: {}'.format(str(e)), 'error')
    except Exception as e:
        logger.error("删除操作异常: {} - {}".format(album_name, str(e)))
        flash('删除操作失败', 'error')

    return redirect(url_for('index'))


# 如果使用自定义目录，添加静态文件路由
@app.route('/custom_static/<path:albumname>/<path:filename>')
def custom_static(albumname, filename):
    """
    加载自定义目录中的静态文件（安全版本）
    
    Args:
        albumname: 相册名称
        filename: 文件名称
        
    Returns:
        Response: 文件响应
    """
    try:
        # 验证是否为图片文件
        if not is_image_file(filename):
            logger.warning("非图片文件访问尝试: {}".format(filename))
            abort(404)

        album_path = Path(ALBUMS_FOLDER) / albumname
        file_path = album_path / filename

        # 验证路径安全性，防止路径遍历
        file_path.resolve().relative_to(album_path.resolve())

        return send_from_directory(album_path, filename)

    except ValueError as e:
        logger.warning("无效的访问尝试: {}/{} - {}".format(albumname, filename, str(e)))
        abort(404)
    except Exception as e:
        logger.error("文件访问异常: {}/{} - {}".format(albumname, filename, str(e)))
        abort(500)


@app.route('/api/album_counts')
def get_album_counts():
    """
    获取所有相册的图片计数
    
    Returns:
        Response: JSON响应，包含所有相册的图片计数
    """
    # 将TTLCache转换为普通字典
    try:
        cache_dict = dict(album_counts_cache)
        return jsonify(cache_dict)
    except Exception as e:
        logger.error("获取相册计数时出错: {}".format(str(e)))
        return jsonify({})


@app.route('/api/count_images/<album_name>')
def api_count_images(album_name):
    """
    获取指定相册的图片计数
    
    Args:
        album_name: 相册名称
        
    Returns:
        Response: JSON响应，包含相册的图片计数
    """
    try:
        album_path = Path(ALBUMS_FOLDER) / album_name

        # 如果缓存中有，直接返回缓存的计数
        if album_name in album_counts_cache:
            return jsonify({"name": album_name, "count": album_counts_cache[album_name]})

        # 如果缓存中没有，启动异步任务计算并返回临时结果
        executor.submit(count_images_in_album, album_path)
        return jsonify({"name": album_name, "count": 0, "status": "counting"})

    except ValueError as e:
        logger.warning("API访问无效相册: {} - {}".format(album_name, str(e)))
        return jsonify({"error": "无效的相册名称"}), 400
    except Exception as e:
        logger.error("API异常: {} - {}".format(album_name, str(e)))
        return jsonify({"error": "内部服务器错误"}), 500


if __name__ == '__main__':
    pre_startup_check()
    print("使用自定义相册目录: {}".format(ALBUMS_FOLDER))
    app.run(host='0.0.0.0', port=5020)
